Анализ окупаемости рекламы приложения Procrastinate Pro+¶
Краткое описание проекта: Несмотря на огромные вложения в рекламу, последние несколько месяцев развлекательное приложение Procrastinate Pro+ терпит убытки. Задача исследования в том, чтобы разобраться в причинах и помочь компании выйти в плюс.
Описание данных:
Структура visits_info_short.csv:
User Id— уникальный идентификатор пользователя,Region— страна пользователя,Device— тип устройства пользователя,Channel— идентификатор источника перехода,Session Start— дата и время начала сессии,Session End— дата и время окончания сессии.
Структура orders_info_short.csv:
User Id— уникальный идентификатор пользователя,Event Dt— дата и время покупки,Revenue— сумма заказа.
Структура costs_info_short.csv:
dt— дата проведения рекламной кампании,Channel— идентификатор рекламного источника,costs— расходы на эту кампанию.
План работы:
- Выгрузка данных и первичное знакомство с данными.
- Изменение типов данных, обработка дубликатов и пропусков.
- Внесение используемых при анализе функций.
- Выгрузка профилей пользователей.
- Анализ распределения пользователей по странам, устройствам и каналам привлечения.
- Расчет расходов на маркетинг по источникам привлечения.
- Анализ окупаемости рекламы, конверсии и удержания пользователей.
- Выводы.
Цель исследования: анализ окупаемости рекламы и поиск новых точек роста.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from scipy import stats as st
from scipy.stats import binom, norm
from math import sqrt
from IPython.display import display
from datetime import datetime, timedelta
Загрузка данных и подготовка к анализу¶
visits = pd.read_csv('visits_info_short.csv')
orders = pd.read_csv('orders_info_short.csv')
costs = pd.read_csv('costs_info_short.csv')
visits.info()
orders.info()
costs.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 309901 entries, 0 to 309900 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 User Id 309901 non-null int64 1 Region 309901 non-null object 2 Device 309901 non-null object 3 Channel 309901 non-null object 4 Session Start 309901 non-null object 5 Session End 309901 non-null object dtypes: int64(1), object(5) memory usage: 14.2+ MB <class 'pandas.core.frame.DataFrame'> RangeIndex: 40212 entries, 0 to 40211 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 User Id 40212 non-null int64 1 Event Dt 40212 non-null object 2 Revenue 40212 non-null float64 dtypes: float64(1), int64(1), object(1) memory usage: 942.6+ KB <class 'pandas.core.frame.DataFrame'> RangeIndex: 1800 entries, 0 to 1799 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 dt 1800 non-null object 1 Channel 1800 non-null object 2 costs 1800 non-null float64 dtypes: float64(1), object(2) memory usage: 42.3+ KB
Предобработка данных¶
Изменение типов данных¶
Из первичного знакомства с данными видно, что у столбцов со временем строковый формат, поэтому его нужно поменять на datetime. Также нужно поменять названия столбцов: применить нижний регистр и заменить пробелы нижним подчеркиванием.
visits.columns = visits.columns.str.lower()
orders.columns = orders.columns.str.lower()
costs.columns = costs.columns.str.lower()
visits.columns = visits.columns.str.replace(' ', '_')
orders.columns = orders.columns.str.replace(' ', '_')
costs.columns = costs.columns.str.replace(' ', '_')
visits['session_start'] = pd.to_datetime(visits['session_start'])
visits['session_end'] = pd.to_datetime(visits['session_end'])
orders['event_dt'] = pd.to_datetime(orders['event_dt'])
costs['dt'] = pd.to_datetime(costs['dt']).dt.date
Обработка пропусков¶
Из первичного знакомства с данными видно, что пропуски отсутствуют.
Обработка дубликатов¶
visits.duplicated().sum()
0
orders.duplicated().sum()
0
costs.duplicated().sum()
0
Явные дубликаты отсутствуют. Из самих данных сложно предоположить наличие неявных дубликатов, можно лишь проверить, что каждый пользователь не относится к двум странам сразу, что было бы странно:
visits.query('user_id.duplicated()==True and region.duplicated()==False')['user_id'].nunique()
0
Предварительная обработка данных показала, что данные очень высокого качества: в них полностью отсутствуют пропуски и дубликаты. Единственное, что пришлось исправить, это поменять тип данных в столбцах с датой и временем.
Функции для расчёта и анализа LTV, ROI, удержания и конверсии¶
def get_profiles(sessions, orders, ad_costs):
# находим параметры первых посещений
profiles = (
sessions.sort_values(by=['user_id', 'session_start'])
.groupby('user_id')
.agg(
{
'session_start': 'first',
'channel': 'first',
'device': 'first',
'region': 'first',
}
)
.rename(columns={'session_start': 'first_ts'})
.reset_index()
)
# для когортного анализа определяем дату первого посещения
# и первый день месяца, в который это посещение произошло
profiles['dt'] = profiles['first_ts'].dt.date
profiles['month'] = profiles['first_ts'].astype('datetime64[M]')
# добавляем признак платящих пользователей
profiles['payer'] = profiles['user_id'].isin(orders['user_id'].unique())
# считаем количество уникальных пользователей
# с одинаковыми источником и датой привлечения
new_users = (
profiles.groupby(['dt', 'channel'])
.agg({'user_id': 'nunique'})
.rename(columns={'user_id': 'unique_users'})
.reset_index()
)
# объединяем траты на рекламу и число привлечённых пользователей
ad_costs = ad_costs.merge(new_users, on=['dt', 'channel'], how='left')
# делим рекламные расходы на число привлечённых пользователей
ad_costs['acquisition_cost'] = ad_costs['costs'] / ad_costs['unique_users']
# добавляем стоимость привлечения в профили
profiles = profiles.merge(
ad_costs[['dt', 'channel', 'acquisition_cost']],
on=['dt', 'channel'],
how='left',
)
# стоимость привлечения органических пользователей равна нулю
profiles['acquisition_cost'] = profiles['acquisition_cost'].fillna(0)
return profiles
def get_retention(
profiles,
sessions,
observation_date,
horizon_days,
dimensions=[],
ignore_horizon=False,
):
# исключаем органиков
profiles = profiles.query('channel!="organic"')
# добавляем столбец payer в передаваемый dimensions список
dimensions = ['payer'] + dimensions
# исключаем пользователей, не «доживших» до горизонта анализа
last_suitable_acquisition_date = observation_date
if not ignore_horizon:
last_suitable_acquisition_date = observation_date - timedelta(
days=horizon_days - 1
)
result_raw = profiles.query('dt <= @last_suitable_acquisition_date')
# собираем «сырые» данные для расчёта удержания
result_raw = result_raw.merge(
sessions[['user_id', 'session_start']], on='user_id', how='left'
)
result_raw['lifetime'] = (
result_raw['session_start'] - result_raw['first_ts']
).dt.days
# функция для группировки таблицы по желаемым признакам
def group_by_dimensions(df, dims, horizon_days):
result = df.pivot_table(
index=dims, columns='lifetime', values='user_id', aggfunc='nunique'
)
cohort_sizes = (
df.groupby(dims)
.agg({'user_id': 'nunique'})
.rename(columns={'user_id': 'cohort_size'})
)
result = cohort_sizes.merge(result, on=dims, how='left').fillna(0)
result = result.div(result['cohort_size'], axis=0)
result = result[['cohort_size'] + list(range(horizon_days))]
result['cohort_size'] = cohort_sizes
return result
# получаем таблицу удержания
result_grouped = group_by_dimensions(result_raw, dimensions, horizon_days)
# получаем таблицу динамики удержания
result_in_time = group_by_dimensions(
result_raw, dimensions + ['dt'], horizon_days
)
# возвращаем обе таблицы и сырые данные
return result_raw, result_grouped, result_in_time
def get_conversion(
profiles,
purchases,
observation_date,
horizon_days,
dimensions=[],
ignore_horizon=False,
):
# исключаем органиков
profiles = profiles.query('channel!="organic"')
# исключаем пользователей, не «доживших» до горизонта анализа
last_suitable_acquisition_date = observation_date
if not ignore_horizon:
last_suitable_acquisition_date = observation_date - timedelta(
days=horizon_days - 1
)
result_raw = profiles.query('dt <= @last_suitable_acquisition_date')
# определяем дату и время первой покупки для каждого пользователя
first_purchases = (
purchases.sort_values(by=['user_id', 'event_dt'])
.groupby('user_id')
.agg({'event_dt': 'first'})
.reset_index()
)
# добавляем данные о покупках в профили
result_raw = result_raw.merge(
first_purchases[['user_id', 'event_dt']], on='user_id', how='left'
)
# рассчитываем лайфтайм для каждой покупки
result_raw['lifetime'] = (
result_raw['event_dt'] - result_raw['first_ts']
).dt.days
# группируем по cohort, если в dimensions ничего нет
if len(dimensions) == 0:
result_raw['cohort'] = 'All users'
dimensions = dimensions + ['cohort']
# функция для группировки таблицы по желаемым признакам
def group_by_dimensions(df, dims, horizon_days):
result = df.pivot_table(
index=dims, columns='lifetime', values='user_id', aggfunc='nunique'
)
result = result.fillna(0).cumsum(axis = 1)
cohort_sizes = (
df.groupby(dims)
.agg({'user_id': 'nunique'})
.rename(columns={'user_id': 'cohort_size'})
)
result = cohort_sizes.merge(result, on=dims, how='left').fillna(0)
# делим каждую «ячейку» в строке на размер когорты
# и получаем conversion rate
result = result.div(result['cohort_size'], axis=0)
result = result[['cohort_size'] + list(range(horizon_days))]
result['cohort_size'] = cohort_sizes
return result
# получаем таблицу конверсии
result_grouped = group_by_dimensions(result_raw, dimensions, horizon_days)
# для таблицы динамики конверсии убираем 'cohort' из dimensions
if 'cohort' in dimensions:
dimensions = []
# получаем таблицу динамики конверсии
result_in_time = group_by_dimensions(
result_raw, dimensions + ['dt'], horizon_days
)
# возвращаем обе таблицы и сырые данные
return result_raw, result_grouped, result_in_time
def get_ltv(
profiles,
purchases,
observation_date,
horizon_days,
dimensions=[],
ignore_horizon=False,
):
# исключаем органиков
profiles = profiles.query('channel!="organic"')
# исключаем пользователей, не «доживших» до горизонта анализа
last_suitable_acquisition_date = observation_date
if not ignore_horizon:
last_suitable_acquisition_date = observation_date - timedelta(
days=horizon_days - 1
)
result_raw = profiles.query('dt <= @last_suitable_acquisition_date')
# добавляем данные о покупках в профили
result_raw = result_raw.merge(
purchases[['user_id', 'event_dt', 'revenue']], on='user_id', how='left'
)
# рассчитываем лайфтайм пользователя для каждой покупки
result_raw['lifetime'] = (
result_raw['event_dt'] - result_raw['first_ts']
).dt.days
# группируем по cohort, если в dimensions ничего нет
if len(dimensions) == 0:
result_raw['cohort'] = 'All users'
dimensions = dimensions + ['cohort']
# функция группировки по желаемым признакам
def group_by_dimensions(df, dims, horizon_days):
# строим «треугольную» таблицу выручки
result = df.pivot_table(
index=dims, columns='lifetime', values='revenue', aggfunc='sum'
)
# находим сумму выручки с накоплением
result = result.fillna(0).cumsum(axis=1)
# вычисляем размеры когорт
cohort_sizes = (
df.groupby(dims)
.agg({'user_id': 'nunique'})
.rename(columns={'user_id': 'cohort_size'})
)
# объединяем размеры когорт и таблицу выручки
result = cohort_sizes.merge(result, on=dims, how='left').fillna(0)
# считаем LTV: делим каждую «ячейку» в строке на размер когорты
result = result.div(result['cohort_size'], axis=0)
# исключаем все лайфтаймы, превышающие горизонт анализа
result = result[['cohort_size'] + list(range(horizon_days))]
# восстанавливаем размеры когорт
result['cohort_size'] = cohort_sizes
# собираем датафрейм с данными пользователей и значениями CAC,
# добавляя параметры из dimensions
cac = df[['user_id', 'acquisition_cost'] + dims].drop_duplicates()
# считаем средний CAC по параметрам из dimensions
cac = (
cac.groupby(dims)
.agg({'acquisition_cost': 'mean'})
.rename(columns={'acquisition_cost': 'cac'})
)
# считаем ROI: делим LTV на CAC
roi = result.div(cac['cac'], axis=0)
# удаляем строки с бесконечным ROI
roi = roi[~roi['cohort_size'].isin([np.inf])]
# восстанавливаем размеры когорт в таблице ROI
roi['cohort_size'] = cohort_sizes
# добавляем CAC в таблицу ROI
roi['cac'] = cac['cac']
# в финальной таблице оставляем размеры когорт, CAC
# и ROI в лайфтаймы, не превышающие горизонт анализа
roi = roi[['cohort_size', 'cac'] + list(range(horizon_days))]
# возвращаем таблицы LTV и ROI
return result, roi
# получаем таблицы LTV и ROI
result_grouped, roi_grouped = group_by_dimensions(
result_raw, dimensions, horizon_days
)
# для таблиц динамики убираем 'cohort' из dimensions
if 'cohort' in dimensions:
dimensions = []
# получаем таблицы динамики LTV и ROI
result_in_time, roi_in_time = group_by_dimensions(
result_raw, dimensions + ['dt'], horizon_days
)
return (
result_raw, # сырые данные
result_grouped, # таблица LTV
result_in_time, # таблица динамики LTV
roi_grouped, # таблица ROI
roi_in_time, # таблица динамики ROI
)
def filter_data(df, window):
# для каждого столбца применяем скользящее среднее
for column in df.columns.values:
df[column] = df[column].rolling(window).mean()
return df
def plot_retention(retention, retention_history, horizon, window=7):
# задаём размер сетки для графиков
plt.figure(figsize=(15, 10))
# исключаем размеры когорт и удержание первого дня
retention = retention.drop(columns=['cohort_size', 0])
# в таблице динамики оставляем только нужный лайфтайм
retention_history = retention_history.drop(columns=['cohort_size'])[
[horizon - 1]
]
# если в индексах таблицы удержания только payer,
# добавляем второй признак — cohort
if retention.index.nlevels == 1:
retention['cohort'] = 'All users'
retention = retention.reset_index().set_index(['cohort', 'payer'])
# в таблице графиков — два столбца и две строки, четыре ячейки
# в первой строим кривые удержания платящих пользователей
ax1 = plt.subplot(2, 2, 1)
retention.query('payer == True').droplevel('payer').T.plot(
grid=True, ax=ax1, cmap='tab20'
)
plt.legend()
plt.xlabel('Лайфтайм')
plt.title('Удержание платящих пользователей')
# во второй ячейке строим кривые удержания неплатящих
# вертикальная ось — от графика из первой ячейки
ax2 = plt.subplot(2, 2, 2, sharey=ax1)
retention.query('payer == False').droplevel('payer').T.plot(
grid=True, ax=ax2, cmap='tab20'
)
plt.legend()
plt.xlabel('Лайфтайм')
plt.title('Удержание неплатящих пользователей')
# в третьей ячейке — динамика удержания платящих
ax3 = plt.subplot(2, 2, 3)
# получаем названия столбцов для сводной таблицы
columns = [
name
for name in retention_history.index.names
if name not in ['dt', 'payer']
]
# фильтруем данные и строим график
filtered_data = retention_history.query('payer == True').pivot_table(
index='dt', columns=columns, values=horizon - 1, aggfunc='mean'
)
filter_data(filtered_data, window).plot(grid=True, ax=ax3, cmap='tab20')
plt.xlabel('Дата привлечения')
plt.title(
'Динамика удержания платящих пользователей на {}-й день'.format(
horizon
)
)
# в чётвертой ячейке — динамика удержания неплатящих
ax4 = plt.subplot(2, 2, 4, sharey=ax3)
# фильтруем данные и строим график
filtered_data = retention_history.query('payer == False').pivot_table(
index='dt', columns=columns, values=horizon - 1, aggfunc='mean'
)
filter_data(filtered_data, window).plot(grid=True, ax=ax4, cmap='tab20')
plt.xlabel('Дата привлечения')
plt.title(
'Динамика удержания неплатящих пользователей на {}-й день'.format(
horizon
)
)
plt.tight_layout()
plt.show()
def plot_conversion(conversion, conversion_history, horizon, window=7):
# задаём размер сетки для графиков
plt.figure(figsize=(15, 5))
# исключаем размеры когорт
conversion = conversion.drop(columns=['cohort_size'])
# в таблице динамики оставляем только нужный лайфтайм
conversion_history = conversion_history.drop(columns=['cohort_size'])[
[horizon - 1]
]
# первый график — кривые конверсии
ax1 = plt.subplot(1, 2, 1)
conversion.T.plot(grid=True, ax=ax1, cmap='tab20')
plt.legend()
plt.xlabel('Лайфтайм')
plt.title('Конверсия пользователей')
# второй график — динамика конверсии
ax2 = plt.subplot(1, 2, 2, sharey=ax1)
columns = [
# столбцами сводной таблицы станут все столбцы индекса, кроме даты
name for name in conversion_history.index.names if name not in ['dt']
]
filtered_data = conversion_history.pivot_table(
index='dt', columns=columns, values=horizon - 1, aggfunc='mean'
)
filter_data(filtered_data, window).plot(grid=True, ax=ax2, cmap='tab20')
plt.xlabel('Дата привлечения')
plt.title('Динамика конверсии пользователей на {}-й день'.format(horizon))
plt.tight_layout()
plt.show()
def plot_ltv_roi(ltv, ltv_history, roi, roi_history, horizon, window=7):
# задаём сетку отрисовки графиков
plt.figure(figsize=(20, 10))
# из таблицы ltv исключаем размеры когорт
ltv = ltv.drop(columns=['cohort_size'])
# в таблице динамики ltv оставляем только нужный лайфтайм
ltv_history = ltv_history.drop(columns=['cohort_size'])[[horizon - 1]]
# стоимость привлечения запишем в отдельный фрейм
cac_history = roi_history[['cac']]
# из таблицы roi исключаем размеры когорт и cac
roi = roi.drop(columns=['cohort_size', 'cac'])
# в таблице динамики roi оставляем только нужный лайфтайм
roi_history = roi_history.drop(columns=['cohort_size', 'cac'])[
[horizon - 1]
]
# первый график — кривые ltv
ax1 = plt.subplot(2, 3, 1)
ltv.T.plot(grid=True, ax=ax1, cmap='tab20')
plt.legend()
plt.xlabel('Лайфтайм')
plt.title('LTV')
# второй график — динамика ltv
ax2 = plt.subplot(2, 3, 2, sharey=ax1)
# столбцами сводной таблицы станут все столбцы индекса, кроме даты
columns = [name for name in ltv_history.index.names if name not in ['dt']]
filtered_data = ltv_history.pivot_table(
index='dt', columns=columns, values=horizon - 1, aggfunc='mean'
)
filter_data(filtered_data, window).plot(grid=True, ax=ax2, cmap='tab20')
plt.xlabel('Дата привлечения')
plt.title('Динамика LTV пользователей на {}-й день'.format(horizon))
# третий график — динамика cac
ax3 = plt.subplot(2, 3, 3, sharey=ax1)
# столбцами сводной таблицы станут все столбцы индекса, кроме даты
columns = [name for name in cac_history.index.names if name not in ['dt']]
filtered_data = cac_history.pivot_table(
index='dt', columns=columns, values='cac', aggfunc='mean'
)
filter_data(filtered_data, window).plot(grid=True, ax=ax3, cmap='tab20')
plt.xlabel('Дата привлечения')
plt.title('Динамика стоимости привлечения пользователей')
# четвёртый график — кривые roi
ax4 = plt.subplot(2, 3, 4)
roi.T.plot(grid=True, ax=ax4, cmap='tab20')
plt.axhline(y=1, color='red', linestyle='--', label='Уровень окупаемости')
plt.legend()
plt.xlabel('Лайфтайм')
plt.title('ROI')
# пятый график — динамика roi
ax5 = plt.subplot(2, 3, 5, sharey=ax4)
# столбцами сводной таблицы станут все столбцы индекса, кроме даты
columns = [name for name in roi_history.index.names if name not in ['dt']]
filtered_data = roi_history.pivot_table(
index='dt', columns=columns, values=horizon - 1, aggfunc='mean'
)
filter_data(filtered_data, window).plot(grid=True, ax=ax5, cmap='tab20')
plt.axhline(y=1, color='red', linestyle='--', label='Уровень окупаемости')
plt.xlabel('Дата привлечения')
plt.title('Динамика ROI пользователей на {}-й день'.format(horizon))
plt.tight_layout()
plt.show()
Исследовательский анализ данных¶
Профили пользователей¶
profiles = get_profiles(visits, orders, costs)
profiles
| user_id | first_ts | channel | device | region | dt | month | payer | acquisition_cost | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 599326 | 2019-05-07 20:58:57 | FaceBoom | Mac | United States | 2019-05-07 | 2019-05-01 | True | 1.088172 |
| 1 | 4919697 | 2019-07-09 12:46:07 | FaceBoom | iPhone | United States | 2019-07-09 | 2019-07-01 | False | 1.107237 |
| 2 | 6085896 | 2019-10-01 09:58:33 | organic | iPhone | France | 2019-10-01 | 2019-10-01 | False | 0.000000 |
| 3 | 22593348 | 2019-08-22 21:35:48 | AdNonSense | PC | Germany | 2019-08-22 | 2019-08-01 | False | 0.988235 |
| 4 | 31989216 | 2019-10-02 00:07:44 | YRabbit | iPhone | United States | 2019-10-02 | 2019-10-01 | False | 0.230769 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 150003 | 999956196527 | 2019-09-28 08:33:02 | TipTop | iPhone | United States | 2019-09-28 | 2019-09-01 | False | 3.500000 |
| 150004 | 999975439887 | 2019-10-21 00:35:17 | organic | PC | UK | 2019-10-21 | 2019-10-01 | False | 0.000000 |
| 150005 | 999976332130 | 2019-07-23 02:57:06 | TipTop | iPhone | United States | 2019-07-23 | 2019-07-01 | False | 2.600000 |
| 150006 | 999979924135 | 2019-09-28 21:28:09 | MediaTornado | PC | United States | 2019-09-28 | 2019-09-01 | False | 0.205714 |
| 150007 | 999999563947 | 2019-10-18 19:57:25 | organic | iPhone | United States | 2019-10-18 | 2019-10-01 | False | 0.000000 |
150008 rows × 9 columns
min_date = profiles['dt'].min() #минимальная дата привлечения
min_date
datetime.date(2019, 5, 1)
max_date = profiles['dt'].max()
max_date
datetime.date(2019, 10, 27)
Получили профили пользователей, проверили, что минимальная дата привлечения 01.05.2019, а максимальная - 27.10.2019, что совпадает с периодом, за которой были собраны данные.
Географическое распределение пользователей¶
Напишем функцию для формирования и вывода на экран сводных таблиц:
def pivot_print(index_name):
table = profiles.pivot_table(index=index_name, values=['user_id', 'payer'],
aggfunc={'user_id':'nunique', 'payer':['mean', 'sum']}).droplevel(1, axis=1)
table['user_ratio'] = (table['user_id']/table['user_id'].sum())
table.columns = ['payer_ratio', 'payer', 'user_id', 'user_ratio']
table['payer_ratio'] = (table['payer_ratio'])
return table
def pivot_per(table):
table = table.style.format({'payer_ratio': '{:,.2%}'.format, 'user_ratio': '{:,.2%}'.format})
return table
region_data = pivot_print('region')
pivot_per(region_data)
| payer_ratio | payer | user_id | user_ratio | |
|---|---|---|---|---|
| region | ||||
| France | 3.80% | 663 | 17450 | 11.63% |
| Germany | 4.11% | 616 | 14981 | 9.99% |
| UK | 3.98% | 700 | 17575 | 11.72% |
| United States | 6.90% | 6902 | 100002 | 66.66% |
region_data.plot.pie(autopct='%.2f%%', y='user_id', ylabel='', legend=False, title='Распределение пользователей по странам',
ax=plt.subplot(1,2,1), figsize=(12,15))
region_data.plot.pie(autopct='%.2f%%', y='payer', ylabel='', legend=False,
title='Распределение платящих пользователей по странам', ax=plt.subplot(1,2,2), figsize=(12,15))
plt.tight_layout()
plt.show()
region_payer = (region_data['payer_ratio']*100).sort_values().plot.barh(title='Рейтинг стран по доле платящих пользователей')
region_payer.set(xlabel='Доля платящих пользователей, %', ylabel='Страна');
Пользователи приходят из Франции, Германии, Великобритании, США. Примерно 2/3 пользователей из США, примерно одинаковое количество пользователей из Великобритании и Франции (11,72% и 11,63% соответственно), оставшиеся 9,99% из Германии.
По количеству платящих пользователей абсолютным лидером также является США (на эту страну приходится примерно 77,72% всех платящих пользователей), остальные страны имеют примерно одинаковое количество платящих пользователей.
По доле платящих абсолютным лидером также является США (6,9%), далее с примерно одинаковыми результатами следуют Германия (4,1%), Великобритания (4%), Франция (3,8%).
Стоит также отметить низкую долю платящих пользователей в целом.
Распределение пользователей по устройствам¶
device_data = pivot_print('device')
pivot_per(device_data)
| payer_ratio | payer | user_id | user_ratio | |
|---|---|---|---|---|
| device | ||||
| Android | 5.85% | 2050 | 35032 | 23.35% |
| Mac | 6.36% | 1912 | 30042 | 20.03% |
| PC | 5.05% | 1537 | 30455 | 20.30% |
| iPhone | 6.21% | 3382 | 54479 | 36.32% |
device_data.plot.pie(autopct='%.2f%%', y='user_id', ylabel='', legend=False,
title='Распределение пользователей по устройствам', ax=plt.subplot(1,2,1), figsize=(12,20))
device_data.plot.pie(autopct='%.2f%%', y='payer', ylabel='', legend=False,
title='Распределение платящих пользователей по устройствам', ax=plt.subplot(1,2,2), figsize=(12,20))
plt.tight_layout()
plt.show()
device_payer = (device_data['payer_ratio']*100).sort_values().plot.barh(title='Рейтинг устройств по доле платящих пользователей')
device_payer.set(xlabel='Доля платящих пользователей, %', ylabel='Устройство');
Пользователи заходят в приложение через Mac, iPhone, Android, PC. Большинство пользователей используют iPhone (36,32%), далее следуют Android (23,35%), PC(20,3%), Mac (20,03%).
По количеству платящих лидером является iPhone (38,08%), далее следуют Android(23,08%), Mac(21,53%), PC(17,31%).
По доле платящих ситуация примерно одинаковая, показатель меняется от 5% на PC до 6,4% на Mac. На iPhone и Android доля платящих составляет примерно 6,2% и 5,9% соответственно.
Распределение пользователей по каналам привлечения¶
channel_data = pivot_print('channel')
pivot_per(channel_data)
| payer_ratio | payer | user_id | user_ratio | |
|---|---|---|---|---|
| channel | ||||
| AdNonSense | 11.34% | 440 | 3880 | 2.59% |
| FaceBoom | 12.20% | 3557 | 29144 | 19.43% |
| LeapBob | 3.06% | 262 | 8553 | 5.70% |
| MediaTornado | 3.57% | 156 | 4364 | 2.91% |
| OppleCreativeMedia | 2.71% | 233 | 8605 | 5.74% |
| RocketSuperAds | 7.91% | 352 | 4448 | 2.97% |
| TipTop | 9.60% | 1878 | 19561 | 13.04% |
| WahooNetBanner | 5.30% | 453 | 8553 | 5.70% |
| YRabbit | 3.83% | 165 | 4312 | 2.87% |
| lambdaMediaAds | 10.47% | 225 | 2149 | 1.43% |
| organic | 2.06% | 1160 | 56439 | 37.62% |
channel_count = channel_data['user_id'].sort_values().plot.barh(title='Рейтинг каналов по количеству пользователей')
channel_count.set(xlabel='Количество пользователей', ylabel='Канал');
channel_payer = channel_data['payer'].sort_values().plot.barh(title='Рейтинг каналов по количеству платящих пользователей')
channel_payer.set(xlabel='Количество платящих пользователей', ylabel='Канал');
channel_payer_ratio = (channel_data['payer_ratio']*100).sort_values().plot.barh(title='Рейтинг каналов по доле платящих пользователей')
channel_payer_ratio.set(xlabel='Доля платящих пользователей, %', ylabel='Канал');
По количеству привлеченных пользователей лидирует органический трафик (37,62% всех пользователей), что встречается довольно часто. Среди платных рекламных каналов лидирует Faceboom (19,43%), также среди остальных выделяется TipTop (13,04%).
По количеству платящих пользователей абсолютное лидерство у Faceboom, далее следуют TipTop и органический трафик.
По доле платящих пользователей лидирует Faceboom (12,2%), также в тройку лидеров входят AdNonSense (11,34%) и lambdaMediaAds (10,47%). Органический трафик на последнем месте (2,06%), однако это обусловлено наибольшим общим количеством пришедших пользователей (как уже было сказано, по количеству платящих пользователей органический трафик на 3-ем месте).
Основной страной присутствия для компании является США (2/3 всех пользователей и 77,72% платящих). Доля платяших в ней также наибольшая (6,9%).
Основным устройством, с которого пользователи используют приложение, является iPhone (36,32% всех пользователей и 38,08% платящих). По доле платящих лидером является Mac (6,36%), iPhone на 2-ом месте (6,21%).
Больше всего пользователей в целом из органического трафика (37,62%), а платящих больше всего пришло через платформу Faceboom
(примерно 3 500 человек), доля платящих самая высокая также у Faceboom (12,2%).
Маркетинг¶
Общая сумма расходов на маркетинг¶
costs['costs'].sum().round(2)
105497.3
На рекламные расходы было потрачено примерно 105 497 у.е.
Распределение рекламных расходов по источникам¶
costs_grouped = costs.groupby('channel').agg({'costs':'sum'}).sort_values(by='costs', ascending=False)
costs_grouped['ratio'] = (costs_grouped['costs']/costs_grouped['costs'].sum()*100).round(2)
costs_grouped
| costs | ratio | |
|---|---|---|
| channel | ||
| TipTop | 54751.30 | 51.90 |
| FaceBoom | 32445.60 | 30.75 |
| WahooNetBanner | 5151.00 | 4.88 |
| AdNonSense | 3911.25 | 3.71 |
| OppleCreativeMedia | 2151.25 | 2.04 |
| RocketSuperAds | 1833.00 | 1.74 |
| LeapBob | 1797.60 | 1.70 |
| lambdaMediaAds | 1557.60 | 1.48 |
| MediaTornado | 954.48 | 0.90 |
| YRabbit | 944.22 | 0.90 |
costs_plot = costs_grouped['costs'].sort_values().plot.barh(title='Рейтинг рекламных источников по затратам')
costs_plot.set(xlabel='Затраты', ylabel='Источник');
По рекламным затратам абсолютным лидером является TipTop (51,9%), далее следует FaceBoom (30,75%), все остальные источники составляют менее 5% взятый каждый в отдельности.
Динамика изменения рекламных расходов по источникам¶
costs['week'] = costs['dt'].astype('datetime64[W]')
costs['month'] = costs['dt'].astype('datetime64[M]')
Так как данные представлены за один 2019 год, то по для анализа динамики данные можно не группировать по году.
costs_week = costs.pivot_table(index='week',
columns='channel',
values='costs',
aggfunc='sum')
costs_week.loc['total', :] = costs_week.sum()
costs_week.sort_values(by='total', axis=1, inplace=True, ascending=False)
costs_week.drop(index='total', inplace=True)
costs_month = costs.pivot_table(index='month',
columns='channel',
values='costs',
aggfunc='sum')
costs_month.loc['total', :] = costs_month.sum()
costs_month.sort_values(by='total', axis=1, inplace=True, ascending=False)
costs_month.drop(index='total', inplace=True)
costs_week.plot.area(grid=True, figsize=(15,6), ax=plt.subplot(1,2,1),
xlabel='Номер месяца', ylabel='Сумма затрат', title='Понедельная динамика затрат по каналам')
costs_month.plot.area(grid=True, figsize=(15,6), ax=plt.subplot(1,2,2),
xlabel='Номер месяца', ylabel='Сумма затрат', title='Помесячная динамика затрат по каналам')
plt.tight_layout()
plt.show()
На обоих графиках в начале выше всех находится график Faceboom, а потом TipTop, далее лидерство перехватывает Tip Top и со временем отрыв TipTop от Faceboom растет.
Понедельный график более изрезанный, а помесячный более плавный, что подтверждает обратную взаимосвязь между шумами на графике и длиной рассматриваемого периода.
На помесячном графике заметен уверенный и стабильный рост источника TipTop с падением в последнем месяце.
Максимальная сумма недельных рекламных затрат составила примерно 6 800 у.е., месячных - примерно 22 000 у.е.
Средняя стоимость привлечения по источнику¶
cac_source = profiles.groupby('channel').agg({'acquisition_cost':'mean'}).sort_values(by='acquisition_cost', ascending=False)
cac_source['acquisition_cost']=cac_source['acquisition_cost'].round(2)
cac_source
| acquisition_cost | |
|---|---|
| channel | |
| TipTop | 2.80 |
| FaceBoom | 1.11 |
| AdNonSense | 1.01 |
| lambdaMediaAds | 0.72 |
| WahooNetBanner | 0.60 |
| RocketSuperAds | 0.41 |
| OppleCreativeMedia | 0.25 |
| YRabbit | 0.22 |
| MediaTornado | 0.22 |
| LeapBob | 0.21 |
| organic | 0.00 |
Самая большая средняя стоимость привлечения пользователя у источника TipTop (2,8 у.е.), далее следует Faceboom (1,11 у.е.) и AdNonSense (1,01 у.е.). TipTop сильно выше остальных, что было заметно и в остальных показателях по рекламным затратам.
Всего на рекламу в рассматриваемый период было потрачено 105 497 у.е. Максимальная сумма затрат за неделю - 6 800 у.е., а за месяц - примерно 22 000 у.е. Самым дорогим с точки зрения рекламных затрат источником оказался TipTop (51,9% всех затрат), далее следует Faceboom (30,75% всех затрат). При этом стоит отметить, что у Faceboom средняя стоимость привлечения составляет 1,11 у.е., что значительно ниже, чем у TipTop (2,8 у.е.).
А количество пользователей, как совокупное, так и платящих, выше у Faceboom. Это позволяет сделать предположение о том, что Faceboom окупается лучше, чем TipTop.
Окупаемость рекламы¶
Органических пользователей в анализ окупаемости включать не нужно, так как рекламные затраты на их привлечение равны нулю.
observation_date = datetime(2019,11,1).date()
horizon = 14
Окупаемость рекламы в целом¶
ltv_raw, ltv, ltv_history, roi, roi_history = get_ltv(profiles, orders, observation_date, horizon)
Проверяем корректность расчета CAC:
max_acquisition_date = roi_history.reset_index()['dt'].max()
print(max_acquisition_date)
2019-10-19
print(
'Общие расходы на привлечение из таблицы ROI: {:.2f}'.format(
roi.loc['All users', 'cac'] * roi.loc['All users', 'cohort_size']
)
)
print(
'Общие расходы на привлечение из таблицы costs: {:.2f}'.format(
costs.query('dt <= @max_acquisition_date')['costs'].sum()
)
)
Общие расходы на привлечение из таблицы ROI: 99085.74 Общие расходы на привлечение из таблицы costs: 99085.74
CAC посчитан верно.
Построим графики LTV, CAC, ROI:
plot_ltv_roi(ltv, ltv_history, roi, roi_history, horizon)
Как видно на графике ROI, реклама, направленная на привлечение пользователей, не окупается в течение заложенного в бизнес-плане срока, равного 2 неделям.
Причина провала рекламной кампании кроется в увеличении рекламных расходов на привлечение пользователя в целом, что видно на графике динамики CAC, который возрастает, начиная с резкого роста в июне. Такой постоянный рост стал причиной постепенного падения ROI на 14 лайфтайм вплоть до уровня ниже окупаемости, начиная с середины июня.
Конверсия пользователей¶
conversion_raw, conversion, conversion_history = get_conversion(profiles, orders, observation_date, horizon)
Проверим правильность расчета конверсии:
print(conversion[conversion > 1].fillna(''))
cohort_size 0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 \
cohort
All users 88644
13.0
cohort
All users
В таблице конверсии остался только размер когорты — CR выше единицы нет.
print(conversion_history[conversion_history > 1].fillna(''))
cohort_size 0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 \
dt
2019-05-01 390
2019-05-02 323
2019-05-03 346
2019-05-04 457
2019-05-05 438
... ... .. .. .. .. .. .. .. .. .. .. ... ...
2019-10-15 442
2019-10-16 436
2019-10-17 449
2019-10-18 591
2019-10-19 588
12.0 13.0
dt
2019-05-01
2019-05-02
2019-05-03
2019-05-04
2019-05-05
... ... ...
2019-10-15
2019-10-16
2019-10-17
2019-10-18
2019-10-19
[172 rows x 15 columns]
В таблице динамики конверсии на месте значений по лайфтаймам одни пропуски. Значит, ни в одной из ячеек нет ошибочного значения конверсии — выше единицы.
Построим графики:
plot_conversion(conversion, conversion_history, horizon)
Конверсия плавно растет с последовательным замедлением роста, на 14 лайфтайм конверсия составила примерно 8%.
График динамики конверсии на 14-й день довольно сильно "шумит", диапазон значений примерно от 6,75% до 9,3%.
Удержание пользователей¶
retention_raw, retention, retention_history = get_retention(profiles, visits, observation_date, horizon)
Построим графики:
plot_retention(retention, retention_history, horizon)
Удержание платящих пользователей на следующий день после привлечения составляет примерно 38%, а через 2 недели - примерно 10%.
Удержание неплатящих пользователей на следующий день после привлечения составляет примерно 17,5%, а через 2 недели - близко к нулю.
На графике динамики удержания видно, что в течение рассматриваемого периода удержание платящих пользователей через 2 недели колебалось от 17,5% до 6,25%.
Удержание неплатящих через 2 недели близко к нулю на протяжении всего рассматриваемого периода.
Окупаемость рекламы с разбивкой по устройствам¶
ltv_raw, ltv, ltv_history, roi, roi_history = get_ltv(profiles, orders, observation_date, horizon, dimensions=['device'])
plot_ltv_roi(ltv, ltv_history, roi, roi_history, horizon)
iPhone, Mac, Android не окупаются в течение 2 недель при том, что LTV у них выше, чем у PC, который единственный окупился. Соответственно дело в более высокой стоимости привлечения пользователей iPhone, Mac, Android.
Таким образом, среди устройств негативное влияение на окупаемость оказывают iPhone, Mac, Android.
retention_raw, retention, retention_history = get_retention(profiles, visits, observation_date, horizon, dimensions=['device'])
plot_retention(retention, retention_history, horizon)
Удержание платящих пользователей на Mac, iPhone, Android ниже, чем на PC. Возможно, интерфейс приложения на данных устройствах является менее удобным и понятным, чем на PC. Однако разница в удержании не настолько велика, чтобы ее можно было рассматривать в качестве основной причины разницы в окупаемости.
conversion_raw, conversion, conversion_history = get_conversion(profiles, orders, observation_date, horizon, dimensions=['device'])
plot_conversion(conversion, conversion_history, horizon)
Пользователи Mac, iPhone, Android конвертируются сильно лучше PC.
Основной причиной неокупаемости iPhone, Mac, Android скорее всего является большее по сравнению с другими устройствами использование более дорогих рекламных платформ, которые стоят дороже (прежде всего, речь идет о TipTop и Faceboom).
Также можно предположить, что маркетологи компании при разработке рекламной кампании ориентируются на коэффициент конверсии, которые у iPhone, Mac, Android лучше, чем у остальных устройств. Заметив, что конверсия по данным устройствам лучше, маркетологи принимают решение вложить в рекламу на данных устройствах большие объемы средств, пытаясь привлечь все больше пользователей и игнорируя при этом, что увеличение рекламных расходов не приводит к желаемому увеличению конверсии и LTV (они растут умеренно теми же темпами, что и другие устройства), в результате чего и получаются негативные показатели окупаемости.
Окупаемость рекламы с разбивкой по странам¶
ltv_raw, ltv, ltv_history, roi, roi_history = get_ltv(profiles, orders, observation_date, horizon, dimensions=['region'])
plot_ltv_roi(ltv, ltv_history, roi, roi_history, horizon)
Реклама в США не окупается, в остальных странах реклама окупается с большим запасом (ROI на 14-й день составляет 140%-170%) при том, что LTV в США заметно выше, чем в остальных странах. Но еще более заметно, что в США выше стоимость привлечения пользователей (в 3 и даже почти в 4 раза), что видно на графике динамики CAC. Именно это и является причиной неокупаемости рекламы в США.
retention_raw, retention, retention_history = get_retention(profiles, visits, observation_date, horizon, dimensions=['region'])
plot_retention(retention, retention_history, horizon)
Удержание платящих пользователей в США заметно ниже, чем в остальных странах. Причиной этого может быть неправильный подход к населению страны и отсутствие учета национальных интересов жителей.
conversion_raw, conversion, conversion_history = get_conversion(profiles, orders, observation_date, horizon, dimensions=['region'])
plot_conversion(conversion, conversion_history, horizon)
Конверсия пользователей в США заметно выше, чем в остальных странах. Конверсия пользователей на 14-й день заметно выросла в июне, когда заметно выросли расходы на рекламу.
Таким образом, США оказывают сильное негативное влияние на окупаемость рекламной кампании всего приложения в целом. Заметно резкое увеличение расходов на рекламу в США при последующем увеличении конверсии пользователей.
Однако клиент должен приносить компании прибыль, а не просто совершать первую покупку, а в США этого не происходит, так как удержание платящих пользователей в США заметно ниже, чем в остальных странах.
Ориентация на показатель конверсии, которую, судя по всему, используют маркетологи компании, не оправдала себя.
Также возможной причиной неудачи рекламной кампании в США является большая по сравнению с другими странами распространенность более дорогих рекламных платформ, которые стоят дороже (прежде всего, речь идет о TipTop и Faceboom).
Если обратиться к географическому распределению пользователей, то на пользователей США приходится 2/3 от их общего количества, а на платящих пользователей даже больше. С учетом высокого уровня окупаемости в других странах, стоит снизить рекламные расходы в США в пользу других стран, или попытаться выйти на новые рынки, если есть такая возможность.
Окупаемость рекламы с разбивкой по рекламным каналам¶
ltv_raw, ltv, ltv_history, roi, roi_history = get_ltv(profiles, orders, observation_date, horizon, dimensions=['channel'])
plot_ltv_roi(ltv, ltv_history, roi, roi_history, horizon)
На графике ROI видно, что не окупились 3 канала: AdNonSense, Faceboom, TipTop (хуже всех). При этом LTV лучше всех у lambdaMediaAds, а на 2-ом месте TipTop. По рекламным расходам абсолютным лидером является TipTop (в начале небольшое преимущество было у Faceboom), на 2-ом месте Faceboom, AdNonSense на 3-ем.
Таким образом, можно сказать о негативном влиянии на окупаемость каналов TipTop, Faceboom, AdNonSense. Это связано прежде всего с CAC, но не только: Faceboom и AdNonSense примерно в середине рейтинга LTV, что явно не является лучшими показателями.
retention_raw, retention, retention_history = get_retention(profiles, visits, observation_date, horizon, dimensions=['channel'])
plot_retention(retention, retention_history, horizon)
Аутсайдерами по удержанию платящих пользователей являются каналы Faceboom и AdNonSense. Это однозначно является одной из причин провальной окупаемости данных платформ и соответственно говорит об их негативном влиянии на окупаемость рекламы приложения в целом.
conversion_raw, conversion, conversion_history = get_conversion(profiles, orders, observation_date, horizon, dimensions=['channel'])
plot_conversion(conversion, conversion_history, horizon)
Лидером по конверсии является Faceboom, далее следуют AdNonSense, lambdaMediaAds, TipTop. То есть вновь повторяется та же история, что и в случае со странами и устройствами: провальные с точки зрения окупаемости и удержания каналы являются лидерами по конверсии.
Таким образом, основной причиной провальной окупаемости нескольких каналов являются высокие расходы на финансирование рекламы через них, а также крайне низкие показатели удержания клиентов.
Причиной столь серьезного финансирования могут быть высокие показатели конверсии у данных каналов, то есть маркетологи могут пытаться развивать каналы с более высокими конверсиями.
Причиной низких показателей удержания платящих пользователей могут быть особенности работы рекламных платформ.
Платформы Faceboom, TipTop, AdNonSense оказывают негативное влияение на окупаемость рекламы приложения в целом.
Маркетологам, возможно, стоит переключиться на другие платформы, у которых хорошие показатели окупаемости. Например, хорошим кандидатом является lambdaMediaAds, у которой высокие показатели конверсии, удержания и окупаемости, а по LTV она вообще на 1-ом месте.
Выводы¶
В процессе предобработки данных были обнаружены следующие проблемы:
- Некорректные типы данных в столбцах с датой и временем, которые пришлось заменить;
- Названия всех столбцов были приведены к snake_case;
- Какие-либо пропуски или дубликаты в данных обнаружены не были, что говорит о высоком качестве данных.
Причины неэффективности привлечения пользователей:
- Неокупаемость рекламных расходов (ROI в целом менее 100%);
- Неправильное использование рекламного бюджета: потенциальные точки роста остаются недофинансированными при избытке финансирования в сектора, которые уже сложно или невозможно развивать. Например, для канала TipTop CAC составляет 3,5 у.е. в конце периода, тогда как в других перспективных каналах этот показатель колеблется от 0 до 1 у.е.;
- При планировании рекламного бюджета во внимание принимается прежде всего высокая конверсия в покупателей, при этом игнорируется тот факт, что причины более высоких уровней конверсии совсем необязательно связаны с более высоким финансированием (рост конверсии во всех секторах примерно одинаковый при огромной разнице в финансировании между некоторыми секторами). Например, самой высокой конверсией на 14-й день среди рекламных платформ обладают Faceboom и AdNonSense (12% и 11% соответственно), тогда как показатель CAC на конец периода у них на уровне чуть более 1 у.е., что является самым высоким показателем за исключением TipTop. Более того у них крайне низкие показатели удержания (менее 5% на 14-й день), тогда как у всех других платформ этот показатель выше 10%. Причинами низкого удержания на платформах могут быть особенности работы платформы;
- При планировании рекламного бюджета в некоторых случаях игнорируются стабильно низкие показатели удержания, т.е. деньги направляются в такие сектора, хотя смысла это не имеет, если они не окупаются. Например, в США показатель удержания на 14-й день составляет менее 10%, тогда как в Великобритании и Франции он равен примерно 18%. При этом CAC для США в конце периода равен 1,7 у.е., а для Великобритании и Франции примерно 0,4 у.е.;
- Топ-3 канала привлечения пользователей по количеству привлекаемых рекламных средств и количеству пользователей в целом одновременно являются 3 каналами, которые не окупаются: TipTop, Faceboom, AdNonSense (остальные каналы окупаются). ROI AdNonSense и Faceboom составляет 80-90%, а у TipTop вообще не более 60%.
- Негативное влияние оказывает также географическое распределение пользователей: в стране основного присутствия США (2/3 всех пользователей и примерно 77% платящих приходится именно на одну эту страну) окупаемость по ROI около 70%, в то время как в других странах она составляет 140% и даже 170%. Обусловлено это высоким уровнем рекламных затрат в США, который, в свою очередь, обусловлен тем, что это основной для компании рынок. Иной причиной плохих показателей окупаемости в США является преимущественное использование рекламных платформ с низкой окупаемостью в этой стране. Также важно понимать, что в США самые низкие показатели удержания, что нельзя не учитывать при планировании рекламного бюджета;
- Реклама на Mac, iPhone, Android также не окупается (ROI 70% у Mac, iPhone, а у Android 90% по сравнению с PC, у которого ROI примерно 105%), в то время как CAC на конец периода у них опять же самый высокий (примерно 1,5 у.е. у Mac, iPhone и 1,2 у.е. у Android по сравнению с 0,9 у.е. для PC).
- Неэффективное распределение бюджетных средств по описанным выше принципам осуществлялось не месяц, и не два: оно продолжается на протяжении всего рассматриваемого периода и нет оснований предпологать, что оно прекратится в будущем. Если ситуацию не исправить, это рано или поздно приведет к закрытию бизнеса. Например, CAC платформы TipTop за рассматриваемый период вырос с 1 у.е. до 3,5 у.е. в конце периода.
Рекомендации для отдела маркетинга:
- При плаинровании рекламного бюджета необходимо принимать во внимание не только конверсию, но и удержание, а также ROI как основной индикатор эффективности рекламной кампании;
- Постоянный и непрерывный анализ эффективности осуществляемых рекламных кампаний, а также поиск новых точек роста вместо следования одним и тем же принципам без их пересмотра;
- Необходимо постепенно сокращать финансиноварие рекламных платформ Faceboom, AdNonSense и TipTop и направить освободившиеся средства в другие потенциально прибыльные платформы. Можно немного увеличить финансирование lambdaMediaAds, которая показывает себя очень хорошо по всем показателям (конверсия на 14-й день чуть более 10%, удержание - почти 20%, ROI почти 250%), которые возможно удастся улучшить еще сильнее. Также не стоит забывать и о новых точках роста, например YRabbit, у которой самый высокий показатель ROI (260%) при низком CAC (примерно 0,25 у.е.). Дальнейшее финансирование этой платформы позволит узнать, увеличится ли конверсия и удержание пользователей на этой платформе и соответственно по приложению в целом или нет;
- Рекомендуется обратить внимание на другие страны, кроме США. Судя по географическому распределению пользователей, США является основным рынком для приложения, однако это не говорит об эффективности дальнейшего финансирования рекламы в этой стране. Наоборот, возможно количество привлеченных пользователей уже достигло своего максимума на данном рынке, поэтому есть смысл постепенно снижать рекламные расходы в пользу любых других стран, как уже имеющихся (Франция, Германия, Великобритания), которые все показывают очень неплохие показатели удержания и окупаемости (ROI от 150 до 170%, удержание от 14% до 18%), так и других, ведь земной шар не ограничен четырьмя странами. Но в последнем случае предварительно стоит провести серьезное исследование потребительского поведения и предпочтений, возможных конкурентов, национальных особенностей ведения бизнеса и обычаев.